﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pandemia.Entidades;
using System.Security.Cryptography;
using System.IO;

namespace Pandemia.Funcionalidad
{
    public class PandemServidor
    {
        #region Singleton

        private static PandemServidor _instancia;

        private PandemServidor()
        {
            GenerarClaves();
            _ultimoIdComando = 0;
        }

        public static PandemServidor ObtenerInstancia()
        {
            if (_instancia == null)
                _instancia = new PandemServidor();

            return _instancia;
        }

        #endregion

        #region Conexiones

        private List<Nodo> _nodos;
        public List<Nodo> Nodos 
        {
            get
            {
                if (_nodos == null)
                    _nodos = new List<Nodo>();
                return _nodos;
            }
            set { _nodos = value; }
        }

        public Nodo RegistrarPing(string alias, string nombreMaquina, string ip, int ID)
        {
            Nodo nodo = TraerNodo(alias, nombreMaquina, ip, ID);

            if (nodo == null)
            {
                nodo = new Nodo();
                nodo.Alias = alias;
                nodo.NombreMaquina = nombreMaquina;
                nodo.Ip = ip;
                nodo.Ping = DateTime.Now;
                nodo.ID = ID;
                Nodos.Add(nodo);
            }
            else
                nodo.Ping = DateTime.Now;

            return nodo;
        }

        public Nodo TraerNodo(string alias, string nombreMaquina, string ip, int ID)
        {
            FiltrarNodosOnline();

            return (from n in ObtenerNodos()
                    where n.Alias.Equals(alias) && n.Ip.Equals(ip) && n.NombreMaquina.Equals(nombreMaquina)
                    && n.ID == ID
                    select n).FirstOrDefault();
        }

        public List<Nodo> ObtenerNodos()
        {
            FiltrarNodosOnline();
            return Nodos;
        }

        private void FiltrarNodosOnline()
        {
            Nodos = (from n in Nodos
                     where (DateTime.Now - n.Ping).Seconds <= 30
                     select n).ToList();
        }

        #endregion

        #region Comandos
        
        private int _ultimoIdComando;
        
        private List<Comando> _comandos;
        public List<Comando> Comandos 
        {
            get
            {
                if (_comandos == null)
                    _comandos = new List<Comando>();

                return _comandos;
            }
        }

        private List<ResultadoComando> _resultados;
        public List<ResultadoComando> Resultados 
        {
            get
            {
                if (_resultados == null)
                    _resultados = new List<ResultadoComando>();

                return _resultados;
            }
        }


        public Comando ObtenerComando(Nodo nodo)
        {
            Comando comando = (from c in Comandos
                               where c.Nodo.Alias.Equals(nodo.Alias)
                               && c.Nodo.Ip.Equals(nodo.Ip)
                               && c.Nodo.NombreMaquina.Equals(nodo.NombreMaquina)
                               && c.Nodo.ID == nodo.ID
                               && c.Estado == EstadoComando.Publicado
                               select c).FirstOrDefault();

            if (comando == null)
            {
                comando = new Comando();
                comando.Estado = EstadoComando.Inactivo;
                comando.Tipo = TipoComando.SinAccion;
            }

            return comando;
        }

        private Comando CrearComando(Nodo nodo)
        {
            Comando nuevoComando = new Comando();
            nuevoComando.IdComando = _ultimoIdComando++;
            nuevoComando.Estado = EstadoComando.Publicado;
            nuevoComando.Nodo = nodo;

            return nuevoComando;
        }

        public void CargarResultado(ResultadoComando resultado)
        {
            Comando comando = Comandos.Where(c => c.IdComando == resultado.Comando.IdComando).FirstOrDefault();

            if (comando == null)
                return;

            comando.Estado = EstadoComando.Procesado;
            resultado.Comando = comando;
            Resultados.Add(resultado);
        }

        public ResultadoComando ObtenerResultado(int idComando)
        {
            return Resultados.Where(r => r.Comando.IdComando == idComando).FirstOrDefault();
        }

        public Comando SolicitarAccion(Nodo nodo, string path, TipoComando tipo)
        {
            Comando nuevoComando = CrearComando(nodo);
            nuevoComando.Tipo = tipo;
            nuevoComando.Path = path;
            Comandos.Add(nuevoComando);

            return nuevoComando;
        }

        #endregion

        #region Seguridad

        private string _clavePrivada;
        private string _clavePublica;

        public string Contraseña { get; set; }
        public int MinutosValidezContraseña { get; set; }

        private void GenerarClaves()
        {
            RSACryptoServiceProvider proveedor = new RSACryptoServiceProvider(1024);
            _clavePrivada = proveedor.ToXmlString(true);
            _clavePublica = proveedor.ToXmlString(false);
        }

        public string ObtenerClavePublica()
        {
            return _clavePublica;
        }

        public bool ValidarToken(string token)
        {
            bool correcto = false;

            try
            {
                RSACryptoServiceProvider proveedor = new RSACryptoServiceProvider(1024);
                proveedor.FromXmlString(_clavePrivada);
                byte[] dataDesencriptada = proveedor.Decrypt(Convert.FromBase64String(token), true);
                string tokenDescriptado = Encoding.ASCII.GetString(dataDesencriptada);

                if (tokenDescriptado.StartsWith(Contraseña))
                {
                    tokenDescriptado = tokenDescriptado.Replace(Contraseña, "");
                    string[] array = tokenDescriptado.Split(new string[] { "-" }, StringSplitOptions.RemoveEmptyEntries);
                    int año = int.Parse(array[0]);
                    int mes = int.Parse(array[1]);
                    int dia = int.Parse(array[2]);
                    int hora = int.Parse(array[3]);
                    int minuto = int.Parse(array[4]);
                    int segundo = int.Parse(array[5]);
                    DateTime fechaToken = new DateTime(año, mes, dia, hora, minuto, segundo);
                    TimeSpan diferencia = DateTime.Now - fechaToken;

                    if (diferencia.Minutes <= MinutosValidezContraseña)
                        correcto = true;
                }
            }
            catch (Exception ex) { }

            return correcto;
        }

        #endregion

        #region Transferencias

        public string CarpetaTransferencias { get; set; }

        public void SubirArchivo(Archivo archivo)
        {
            string carpetaTransferencia = AppDomain.CurrentDomain.BaseDirectory + @"\" + CarpetaTransferencias + @"\";
            string nombreCompleto = carpetaTransferencia + archivo.NombreFichero;

            if (!Directory.Exists(carpetaTransferencia))
                Directory.CreateDirectory(carpetaTransferencia);

            if (File.Exists(nombreCompleto))
                File.Delete(nombreCompleto);

            int leidos = 0;
            int lenght = 2048;
            byte[] buffer = new byte[lenght];

            using (FileStream writer = new FileStream(nombreCompleto, FileMode.CreateNew, FileAccess.Write))
                do
                {
                    leidos = archivo.Bytes.Read(buffer, 0, lenght);
                    writer.Write(buffer, 0, leidos);
                } while (leidos != 0);
        }

        public Archivo Descargar(string nombre)
        {
            string carpetaTransferencia = AppDomain.CurrentDomain.BaseDirectory + @"\" + CarpetaTransferencias + @"\";
            string nombreCompleto = carpetaTransferencia + nombre;
            Archivo archivo = new Archivo();

            if (!File.Exists(nombreCompleto))
                throw new Exception("No existe un archivo con el nombre " + nombre + " en el servidor");

            archivo.Bytes = new FileStream(nombreCompleto, FileMode.Open, FileAccess.Read);
            archivo.NombreFichero = nombre;

            return archivo;
        }

        #endregion
    }
}
